﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using OpenCompiler.CodeGen.Exceptions;

namespace OpenCompiler.CodeGen.Implementation
{
	public class TypeBuilder<T> : TypeBuilder, ITypeBuilder<T>
		where T:class
	{
		internal TypeBuilder()
		{
			this.Implement<T>();
		}


		public new ITypeBuilder<T> Implement<I>() where I : class
		{
			((ITypeBuilder)this).Implement<I>();
			return this;
		}

		public new ITypeBuilder<T> InheritFrom<C>() where C : class
		{
			throw new System.NotImplementedException();
		}

		public new ITypeBuilder<T> ComplementWith<P>() where P : new()
		{
			throw new System.NotImplementedException();
		}

		public new ITypeBuilder<T> WithName(string name)
		{
			this.Name = name;
			return this;
		}

		public new ITypeBuilder<T> VisibleTo(Visibility visibility)
		{
			this.Visibility = visibility;
			return this;
		}


		protected T CreateMethodBuilderFromExpression<T>(Expression method, Func<T> createDelegate)
			where T: MethodBuilder
		{
			var name = this.GetMethodNameFromExpression(method);
			var methodBuilder = createDelegate().WithName(name);
			this.MethodBuilders[name] = methodBuilder;
			return methodBuilder as T;
			
		}

		public IMethodBuilder<T> Method(Expression<Action<T>> method)
		{
			return this.CreateMethodBuilderFromExpression(method, () => new MethodBuilder<T>(this));
		}

		public IMethodBuilder<T, Expression<Action<T, T1>>> Method<T1>(Expression<Action<T, T1>> method)
		{
			return this.CreateMethodBuilderFromExpression(method, 
				() => new MethodBuilder<T, Expression<Action<T,T1>>>(this));
		}

		public IMethodBuilder<T, Expression<Action<T, T1, T2>>> Method<T1, T2>(Expression<Action<T, T1, T2>> method)
		{
			return this.CreateMethodBuilderFromExpression(method,
				() => new MethodBuilder<T, Expression<Action<T, T1, T2>>>(this));
		}

		public IMethodBuilder<T, Expression<Action<T, T1, T2, T3>>> Method<T1, T2, T3>(Expression<Action<T, T1, T2, T3>> method)
		{
			return this.CreateMethodBuilderFromExpression(method,
				() => new MethodBuilder<T, Expression<Action<T, T1, T2, T3>>>(this));
		}

		public IMethodBuilder<T, Expression<Func<T, TResult>>> Method<TResult>(Expression<Func<T, TResult>> method)
		{
			return this.CreateMethodBuilderFromExpression(method,
				() => new MethodBuilder<T, Expression<Func<T, TResult>>>(this));
		}

		public IMethodBuilder<T, Expression<Func<T, T1, TResult>>> Method<T1, TResult>(Expression<Func<T, T1, TResult>> method)
		{
			return this.CreateMethodBuilderFromExpression(method,
				() => new MethodBuilder<T, Expression<Func<T, T1, TResult>>>(this));
		}

		public IMethodBuilder<T,Expression<Func<T, T1, T2, TResult>>> Method<T1, T2, TResult>(Expression<Func<T, T1, T2, TResult>> method)
		{
			return this.CreateMethodBuilderFromExpression(method,
				() => new MethodBuilder<T, Expression<Func<T, T1, T2, TResult>>>(this));
		}

		public IMethodBuilder<T, Expression<Func<T, T1, T2, T3, TResult>>> Method<T1, T2, T3, TResult>(Expression<Func<T, T1, T2, T3, TResult>> method)
		{
			return this.CreateMethodBuilderFromExpression(method,
				() => new MethodBuilder<T, Expression<Func<T, T1, T2, T3, TResult>>>(this));
		}
	}

	public class TypeBuilder : ITypeBuilder
	{
		private List<IInterface> _interfaces;
		protected Dictionary<string, IMethodBuilder> MethodBuilders { get; private set; }

		internal TypeBuilder()
		{
			this._interfaces = new List<IInterface>();
			this.MethodBuilders = new Dictionary<string, IMethodBuilder>();
		}


		public ITypeBuilder Implement<I>() where I : class
		{
			IInterface theInterface = new Interface(typeof (I));
			this._interfaces.Add(theInterface);
			return this;
		}

		public ITypeBuilder InheritFrom<C>() where C : class
		{
			throw new System.NotImplementedException();
		}

		public ITypeBuilder ComplementWith<P>() where P : new()
		{
			throw new System.NotImplementedException();
		}

		public ITypeBuilder WithName(string name)
		{
			this.Name = name;
			return this;
		}

		public ITypeBuilder Implement(Type interfaceType)
		{
			throw new System.NotImplementedException();
		}

		public ITypeBuilder ComplementWith(Type complementingType)
		{
			throw new System.NotImplementedException();
		}

		public ITypeBuilder VisibleTo(Visibility visibility)
		{
			this.Visibility = visibility;
			return this;
		}

		public IMethodBuilder Method(string name)
		{
			var methodBuilder = new MethodBuilder(this).WithName(name);
			this.MethodBuilders[name] = methodBuilder;
			return methodBuilder;
		}

		public IMethodBuilder<Expression<Func<TResult>>> Method<TResult>(Expression<Func<TResult>> method)
		{
			throw new System.NotImplementedException();
		}


		public IPropertyBuilder Property(string name)
		{
			throw new System.NotImplementedException();
		}


		public string Name { get; protected set; }

		public Visibility Visibility { get; protected set; }
		public IInterface[] Interfaces
		{
			get { return this._interfaces.ToArray(); }
		}

		public IMethodBuilder[] Methods
		{
			get { return this.MethodBuilders.Values.ToArray(); }
		}

		public IPropertyBuilder[] Properties
		{
			get { throw new System.NotImplementedException(); }
		}

		private void ValidateMethodExpressionIsLambda(Expression expression)
		{
			if (!(expression is LambdaExpression))
			{
				throw new InvalidExpressionException("Method expression should be a LambdaExpression");
			}
		}

		private void ValidateMethodExpressionIsMethodCall(Expression expression)
		{
			var lambda = expression as LambdaExpression;
			if (lambda.Body.NodeType != ExpressionType.Call)
			{
				throw new InvalidExpressionException("Method expression should be a call expression");
			}

		}

		private void ValidateMethodExpression(Expression expression)
		{
			this.ValidateMethodExpressionIsLambda(expression);
			this.ValidateMethodExpressionIsMethodCall(expression);

		}


		protected string GetMethodNameFromExpression(Expression expression)
		{
			this.ValidateMethodExpression(expression);

			var lambda = expression as LambdaExpression;
			var callExpression = lambda.Body as MethodCallExpression;
			return callExpression.Method.Name;
		}

	}
}
